package com.thinkingcoder.tccache.server;

import static com.thinkingcoder.tccache.constant.EncodeConstants.ARRAY_NULL_RET;
import static com.thinkingcoder.tccache.constant.EncodeConstants.ARRAY_PREFIX;
import static com.thinkingcoder.tccache.constant.EncodeConstants.ARRAY_ZERO_LEN_RET;
import static com.thinkingcoder.tccache.constant.EncodeConstants.BULK_STRING_PREFIX;
import static com.thinkingcoder.tccache.constant.EncodeConstants.CRLF;
import static com.thinkingcoder.tccache.constant.EncodeConstants.ERROR_PREFIX;
import static com.thinkingcoder.tccache.constant.EncodeConstants.INTEGER_PREFIX;
import static com.thinkingcoder.tccache.constant.EncodeConstants.OK;
import static com.thinkingcoder.tccache.constant.EncodeConstants.SIMPLE_STRING_PREFIX;
import static com.thinkingcoder.tccache.constant.EncodeConstants.STRING_NULL_RET;
import static com.thinkingcoder.tccache.constant.EncodeConstants.STRING_ZERO_LEN_RET;

import com.thinkingcoder.tccache.core.Commands;
import com.thinkingcoder.tccache.core.TcCache;
import com.thinkingcoder.tccache.core.command.Command;
import com.thinkingcoder.tccache.core.model.Reply;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;

public class TcCacheHandler extends SimpleChannelInboundHandler<String> {

    public static final TcCache cache = new TcCache();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String message) throws Exception {

        System.out.println("ctx.attribute name => " + ctx.channel().attr(AttributeKey.valueOf("name")).get());

        String[] args = message.split(CRLF);
        System.out.println("TcCacheHandler => " + String.join(",", args));

        String cmd = args[2].toUpperCase();

        Command command = Commands.get(cmd);
        if (command != null) {
            try {
                Reply<?> reply = command.exec(cache, args);
                System.out.println("CMD[" + cmd + "] => " + reply.getType() + " => " + reply.getValue());
                replyContext(ctx, reply);
            } catch (Exception e) {
                Reply<String> error = Reply.error("ERR exception with msg: '" + e.getMessage() + "'");
                replyContext(ctx, error);
            }
        } else {
            Reply<String> error = Reply.error("ERR unsupported command: '" + cmd + "'");
            replyContext(ctx, error);
        }

    }

    private void replyContext(ChannelHandlerContext ctx, Reply<?> reply) {
        switch (reply.getType()) {
            case INT:
                integer(ctx, (int)reply.getValue());
                break;
            case ERROR:
                error(ctx, (String)reply.getValue());
                break;
            case SIMPLE_STRING:
                simpleString(ctx, (String)reply.getValue());
                break;
            case BULK_STRING:
                bulkString(ctx, (String)reply.getValue());
                break;
            case ARRAY:
                array(ctx, (String[])reply.getValue());
                break;
            default:
                simpleString(ctx, OK);
        }
    }

    private void error(ChannelHandlerContext ctx, String msg) {
        writeByteBuf(ctx, errorEncode(msg));
    }

    private String errorEncode(String msg) {
        return ERROR_PREFIX + msg + CRLF;
    }

    private void integer(ChannelHandlerContext ctx, int i) {
        writeByteBuf(ctx, integerEncode(i));
    }

    private static String integerEncode(int i) {
        return INTEGER_PREFIX + i + CRLF;
    }

    private void array(ChannelHandlerContext ctx, String[] array) {

        writeByteBuf(ctx, arrayEncode(array));
    }

    private static String arrayEncode(Object[] objects) {
        StringBuilder sb = new StringBuilder();

        if (objects == null) {
            sb.append(ARRAY_NULL_RET + CRLF);
        } else if (objects.length == 0) {
            sb.append(ARRAY_ZERO_LEN_RET + CRLF);
        } else {
            sb.append(ARRAY_PREFIX + objects.length + CRLF);
            for (int i = 0; i < objects.length; i++) {
                Object object = objects[i];
                if (object == null) {
                    sb.append(STRING_NULL_RET + CRLF);
                } else {
                    if (object instanceof Integer integerObj) {
                        sb.append(integerEncode(integerObj));
                    } else if (object instanceof String stringObj) {
                        sb.append(bulkStringEncode(stringObj));
                    } else if (object instanceof Object[] arrayObj) {
                        sb.append(arrayEncode(arrayObj));
                    }
                }
            }
        }
        return sb.toString();
    }

    private void bulkString(ChannelHandlerContext ctx, String content) {
        writeByteBuf(ctx, bulkStringEncode(content));
    }

    private static String bulkStringEncode(String content) {
        String ret;
        if (content == null) {
            ret = STRING_NULL_RET;
        } else if (content.isEmpty()) {
            ret = STRING_ZERO_LEN_RET;
        } else {
            ret = BULK_STRING_PREFIX + content.getBytes().length + CRLF + content;
        }
        return ret + CRLF;
    }

    private void simpleString(ChannelHandlerContext ctx, String content) {
        writeByteBuf(ctx, simpleStringEncode(content));
    }

    private static String simpleStringEncode(String content) {
        String ret;
        if (content == null) {
            ret = STRING_NULL_RET;
        } else if (content.isEmpty()) {
            ret = STRING_ZERO_LEN_RET;
        } else {
            ret = SIMPLE_STRING_PREFIX + content;
        }
        return ret + CRLF;
    }

    private void writeByteBuf(ChannelHandlerContext ctx, String content) {
        System.out.println("wrap byte buffer and reply: " + content);
        ByteBuf buffer = Unpooled.buffer(128);
        buffer.writeBytes(content.getBytes());
        ctx.writeAndFlush(buffer);
    }
}
